/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/window.hh"

#include "carina/renderer-base.hh"

#include <Windowsx.h>

namespace Carina
{
WindowManager::WindowManager(const string& icon)
{
    WNDCLASSEX  wclass;

    wclass.cbSize = sizeof(WNDCLASSEX);
    wclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC;
    wclass.lpfnWndProc = &Window::WindowProc;
    wclass.cbClsExtra = 0;
    wclass.cbWndExtra = 0;
    wclass.hInstance = (HINSTANCE)GetModuleHandle(nullptr);
    if(!icon.empty())
    {
         wclass.hIcon = static_cast<HICON>(LoadImage(nullptr, icon.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE));
         if(!wclass.hIcon)
             THROW_EXCEPTION("invalid icon file: " + icon);
    }
    else
        wclass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    wclass.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wclass.lpszMenuName = nullptr;
    wclass.lpszClassName = "CarinaWindow";
    wclass.hIconSm = LoadIcon(nullptr, IDI_WINLOGO);

    if(!RegisterClassEx(&wclass))
        THROW_EXCEPTION("cannot register CarinaWindow class");
}

WindowPtr WindowManager::createWindow(const RenderingLibraryPtr& lib, const WindowDescription& desc)
{
    return make_aligned_shared<Window>(*this, lib->createWindowImpl(), desc);
}

Window::Window(WindowManager& wmgr, const WindowImplPtr& wimpl, const WindowDescription& desc)
    :   m_WImpl(wimpl),
        m_CurCursorX(0),
        m_CurCursorY(0)
{
    assert(wimpl);
    m_WImpl->create(wmgr, desc.width, desc.height, desc.samples);
    m_WImpl->setName(desc.name);

    SetWindowLongPtr(m_WImpl->getWindowId(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));

    m_WImpl->show();
}

Window::~Window()
{
    m_WImpl->_destroyW32();
    m_WImpl->destroy();
}

void Window::swapBuffers()
{
    MSG msg;
    m_WImpl->swapBuffers();
    while(PeekMessage(&msg, m_WImpl->getWindowId(), 0, 0, PM_REMOVE))
        DispatchMessage(&msg);
}

KeyboardKey Window::getKey(int key)
{
    switch(key)
    {
    case VK_BACK: return CE_KEY_BACKSPACE;
    case VK_TAB: return CE_KEY_TAB;
    case VK_RETURN: return CE_KEY_ENTER;
    case VK_LCONTROL: return CE_KEY_LCTRL;
    case VK_RCONTROL: return CE_KEY_RCTRL;
    case VK_LSHIFT: return CE_KEY_LSHIFT;
    case VK_RSHIFT: return CE_KEY_RSHIFT;
    case VK_LMENU: return CE_KEY_LALT;
    case VK_RMENU: return CE_KEY_RALT;
    case VK_PAUSE: return CE_KEY_PAUSE;
    case VK_CAPITAL: return CE_KEY_CAPSLOCK;
    case VK_ESCAPE: return CE_KEY_ESCAPE;
    case VK_SPACE: return CE_KEY_SPACE;
    case VK_PRIOR: return CE_KEY_PAGEUP;
    case VK_NEXT: return CE_KEY_PAGEDOWN;
    case VK_END: return CE_KEY_END;
    case VK_HOME: return CE_KEY_HOME;
    case VK_LEFT: return CE_KEY_LEFT;
    case VK_RIGHT: return CE_KEY_RIGHT;
    case VK_UP: return CE_KEY_UP;
    case VK_DOWN: return CE_KEY_DOWN;
    case VK_SNAPSHOT: return CE_KEY_PRINTSCREEN;
    case VK_INSERT: return CE_KEY_INSERT;
    case VK_DELETE: return CE_KEY_DELETE;
    case '0': return CE_KEY_0;
    case '1': return CE_KEY_1;
    case '2': return CE_KEY_2;
    case '3': return CE_KEY_3;
    case '4': return CE_KEY_4;
    case '5': return CE_KEY_5;
    case '6': return CE_KEY_6;
    case '7': return CE_KEY_7;
    case '8': return CE_KEY_8;
    case '9': return CE_KEY_9;
    case 'A': return CE_KEY_A;
    case 'B': return CE_KEY_B;
    case 'C': return CE_KEY_C;
    case 'D': return CE_KEY_D;
    case 'E': return CE_KEY_E;
    case 'F': return CE_KEY_F;
    case 'G': return CE_KEY_G;
    case 'H': return CE_KEY_H;
    case 'I': return CE_KEY_I;
    case 'J': return CE_KEY_J;
    case 'K': return CE_KEY_K;
    case 'L': return CE_KEY_L;
    case 'M': return CE_KEY_M;
    case 'N': return CE_KEY_N;
    case 'O': return CE_KEY_O;
    case 'P': return CE_KEY_P;
    case 'Q': return CE_KEY_Q;
    case 'R': return CE_KEY_R;
    case 'S': return CE_KEY_S;
    case 'T': return CE_KEY_T;
    case 'U': return CE_KEY_U;
    case 'V': return CE_KEY_V;
    case 'W': return CE_KEY_W;
    case 'X': return CE_KEY_X;
    case 'Y': return CE_KEY_Y;
    case 'Z': return CE_KEY_Z;
    case VK_LWIN: return CE_KEY_LWIN;
    case VK_RWIN: return CE_KEY_RWIN;
    case VK_NUMPAD0: return CE_KEY_NUM0;
    case VK_NUMPAD1: return CE_KEY_NUM1;
    case VK_NUMPAD2: return CE_KEY_NUM2;
    case VK_NUMPAD3: return CE_KEY_NUM3;
    case VK_NUMPAD4: return CE_KEY_NUM4;
    case VK_NUMPAD5: return CE_KEY_NUM5;
    case VK_NUMPAD6: return CE_KEY_NUM6;
    case VK_NUMPAD7: return CE_KEY_NUM7;
    case VK_NUMPAD8: return CE_KEY_NUM8;
    case VK_NUMPAD9: return CE_KEY_NUM9;
    case VK_F1: return CE_KEY_F1;
    case VK_F2: return CE_KEY_F2;
    case VK_F3: return CE_KEY_F3;
    case VK_F4: return CE_KEY_F4;
    case VK_F5: return CE_KEY_F5;
    case VK_F6: return CE_KEY_F6;
    case VK_F7: return CE_KEY_F7;
    case VK_F8: return CE_KEY_F8;
    case VK_F9: return CE_KEY_F9;
    case VK_F10: return CE_KEY_F10;
    case VK_F11: return CE_KEY_F11;
    case VK_F12: return CE_KEY_F12;
    case VK_NUMLOCK: return CE_KEY_NUMLOCK;
    case VK_SCROLL: return CE_KEY_SCROLLLOCK;
    }

    return static_cast<KeyboardKey>(-1);
}

LRESULT CALLBACK CE_FORCE_ALIGNMENT Window::WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    Window* window = reinterpret_cast<Window*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
    if(window)
        switch(message)
        {
        case WM_CLOSE:
        {
            RAISE_EVENT(window, QuitEvent, QuitEvent());
            return 0;
        } break;
        case WM_SIZE:
        {
            size_t  width = LOWORD(lParam),
                    height = HIWORD(lParam);
            //RECT rect;
            //GetClientRect(hwnd, &rect);
            //size_t width = rect.right - rect.left,
            //     height = rect.bottom - rect.top;
            window->m_WImpl->resize(width, height);
            RAISE_EVENT(window, ResizeWindowEvent, ResizeWindowEvent(width, height));
            return 0;
        } break;
        case WM_KEYDOWN:
        {
            if(lParam & (1 << 30)) // CHECK: naive?
                return 0;
            if(RAISE_EVENT(window, KeyboardEvent, KeyboardEvent(Window::getKey((int)wParam), true)))
                return 0;
        } break;
        case WM_KEYUP:
        {
            if(RAISE_EVENT(window, KeyboardEvent, KeyboardEvent(Window::getKey((int)wParam), false)))
                return 0;
        } break;
        case WM_MOUSEMOVE:
        {
            int x = GET_X_LPARAM(lParam),
                y = GET_Y_LPARAM(lParam),
                xrel = x - window->m_CurCursorX,
                yrel = y - window->m_CurCursorY;
            window->m_CurCursorX = x;
            window->m_CurCursorY = y;
            if(RAISE_EVENT(window, MouseMotionEvent, MouseMotionEvent(x, y, xrel, yrel)))
                return 0;
        } break;
        }

    return DefWindowProc(hwnd, message, wParam, lParam);
}


WindowImpl::WindowImpl()
    :   m_DC(nullptr),
        m_Window(0) {}

void WindowImpl::setName(const string& name)
{
    SetWindowText(m_Window, name.c_str());
}

void WindowImpl::show()
{
    ShowWindow(m_Window, SW_SHOW);
    UpdateWindow(m_Window);
}

void WindowImpl::_destroyW32()
{
    DestroyWindow(m_Window);
}

size_t WindowImpl::getWidth() const
{
    RECT rect;
    GetClientRect(m_Window, &rect);
    return rect.right-rect.left;
}

size_t WindowImpl::getMSAA() const
{
    return m_MSAA;
}

size_t WindowImpl::getHeight() const
{
    RECT rect;
    GetClientRect(m_Window, &rect);
    return rect.bottom-rect.top;
}
}
